// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... namespace LargoCommon.Midi { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Linq; using JetBrains.Annotations; using Music; /// /// Midi Blocks Solver. /// public sealed class MidiBlocksSolver { #region Fields /// /// Tempo Serious Change Tolerance. /// private const int TempoSeriousChangeTolerance = 25; //// 20- 30 //// private const int tempoChangeTolerance = 10; //// 10 /// /// Tempo Block Change Tolerance. /// private const int TempoBlockChangeLimit = 60; /// /// Minimum Block Length. /// private const int MinimumBlockLength = 20; /// /// Break events. /// private readonly IEnumerable breakEvents; /// /// Block Record. /// private BlockRecord currentRecord; /// /// Block Record. /// private BlockRecord lastRecord; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The given sequence. /// The given break events. public MidiBlocksSolver(CompactMidiStrip givenSequence, IEnumerable givenBreakEvents) { this.Sequence = givenSequence; this.breakEvents = givenBreakEvents; } /// /// Initializes a new instance of the class. /// [UsedImplicitly] public MidiBlocksSolver() { } #endregion #region Properties /// /// Gets or sets the header. /// /// /// The header. /// public CompactMidiStrip Sequence { get; set; } /// /// Gets the main events. /// /// /// Property description. /// private IEnumerable MainEvents { get { var selectedEvents = new List(); MidiEvent lastEvent = null; var lastTempo = 0; foreach (var ev in this.breakEvents.Where(ev => ev != null)) { this.ReadCurrentValues(ev); if (string.CompareOrdinal(this.currentRecord.EventType, "MetaTempo") == 0) { if (lastEvent == null) { selectedEvents.Add(ev); lastEvent = ev; lastTempo = this.currentRecord.Tempo; continue; } var tempoChange = Math.Abs(lastTempo - this.currentRecord.Tempo); if (tempoChange <= 0) { continue; } if (tempoChange <= TempoSeriousChangeTolerance) { continue; } selectedEvents.Add(ev); lastTempo = this.currentRecord.Tempo; } else { selectedEvents.Add(ev); } } return selectedEvents; } } #endregion /// /// Determines the blocks. /// /// The final event. /// /// Returns value. /// public IEnumerable DetermineBlocks(IMidiEvent finalEvent) { var mainEvents = this.MainEvents; var midiBlocks = new Collection(); this.currentRecord = new BlockRecord(string.Empty, 4, 2, TonalityKey.None, 0); this.lastRecord = new BlockRecord(string.Empty, 4, 2, TonalityKey.None, 0); var lastBarNumber = 1; long lastStartTime = 0; MidiBlock midiBlock = null; int barNumber, barDifference; foreach (var ev in mainEvents.Where(ev => ev != null)) { this.ReadCurrentValues(ev); barNumber = this.DetermineBarNumber(this.Sequence.Header.Division, ev); barDifference = barNumber - lastBarNumber; var streamChange = this.DetermineTypeOfChange(this.lastRecord.Tempo, this.lastRecord.MetricBeat, this.lastRecord.MetricBase); //// this.lastRecord.TonalityKey if (streamChange != MidiStreamChange.Serious) { //// || (streamChange == MidiStreamChange.Common && (barDifference > 31) continue; } if (barDifference > 0) { //// Check tempo in the previous block midiBlock?.CheckTempo(); if (this.Sequence.Header.Clone() is MusicalHeader header) { header.Metric.MetricBase = this.lastRecord.MetricBase; header.Metric.MetricBeat = this.lastRecord.MetricBeat; midiBlock = new MidiBlock( lastBarNumber, header, this.lastRecord.TonalityKey, this.lastRecord.Tempo) { MidiTimeFrom = lastStartTime, MidiTimeTo = ev.StartTime, Area = { BarTo = barNumber - 1 } }; } midiBlocks.Add(midiBlock); if (midiBlock?.Header != null) { midiBlock.Header.Number = midiBlocks.Count; } lastBarNumber = barNumber; //// 2013/03 lastStartTime = ev.StartTime; } //// Properties of the last block //// lastBarNumber = barNumber; 2013/03 this.lastRecord.GetValuesFrom(this.currentRecord); } //// Check tempo in the previous block midiBlock?.CheckTempo(); // ReSharper disable once InvertIf if (finalEvent != null) { this.ReadCurrentValues(finalEvent); barNumber = this.DetermineBarNumber(this.Sequence.Header.Division, finalEvent); barDifference = barNumber - lastBarNumber; // ReSharper disable once InvertIf if (barDifference > 0) { if (this.Sequence.Header.Clone() is MusicalHeader musicHeader) { musicHeader.Metric.MetricBase = this.lastRecord.MetricBase; musicHeader.Metric.MetricBeat = this.lastRecord.MetricBeat; midiBlock = new MidiBlock( lastBarNumber, musicHeader, this.lastRecord.TonalityKey, this.lastRecord.Tempo) { MidiTimeFrom = lastStartTime, MidiTimeTo = finalEvent.StartTime, Area = { BarTo = barNumber } }; } if (midiBlock != null) { midiBlock.CheckTempo(); midiBlocks.Add(midiBlock); midiBlock.Header.Number = midiBlocks.Count; } } } var blocks = OptimizeBlocks(midiBlocks); var determineBlocks = blocks.ToList(); foreach (var block in determineBlocks) { block.Sequence = new CompactMidiStrip(this.Sequence.Format, this.Sequence.Header.Division); foreach (var originTrack in this.Sequence) { var track = new MidiTrack(); //// 2020/02 E.g. instruments can be defined in any previous block var zeroEvents = (from ev in originTrack.Events where ev.StartTime < block.MidiTimeFrom && ev.EventType == "VoiceProgramChange" orderby ev.StartTime select ev).ToList(); foreach (var ev in zeroEvents) { //// 2019/02 ev.StartTime = 0; } track.Events.AddRange(zeroEvents); var events = (from ev in originTrack.Events where ev.StartTime >= block.MidiTimeFrom && ev.StartTime <= block.MidiTimeTo orderby ev.StartTime ////, tone.Duration select ev).ToList(); foreach (var ev in events) { //// 2019/02 ev.StartTime = ev.StartTime - block.MidiTimeFrom; } track.Events.AddRange(events); track.Events.RecomputeDeltaTimes(); //// 2019/02 block.Sequence.AddTrack(track); } } return determineBlocks; } #region Private Static methods /// /// Merges the blocks. /// /// The midi blocks. /// Returns value. private static IEnumerable OptimizeBlocks(IEnumerable midiBlocks) { Contract.Requires(midiBlocks != null); var blocks = midiBlocks.Where(block => block != null).ToList(); if (blocks.Count <= 1) { return blocks; } var mergedBlocks = new List(); MidiBlock lastBlock = null; foreach (var block in blocks) { if (lastBlock == null) { mergedBlocks.Add(block); block.Header.Number = mergedBlocks.Count; lastBlock = block; continue; } if (block.Header.Metric.MetricBase == lastBlock.Header.Metric.MetricBase && block.Header.Metric.MetricBeat == lastBlock.Header.Metric.MetricBeat && block.TonalityKey == lastBlock.TonalityKey && Math.Abs(block.Tempo - lastBlock.Tempo) < TempoBlockChangeLimit) { if (lastBlock.Area.Length < MinimumBlockLength || block.Area.Length < MinimumBlockLength || lastBlock.Tempo == block.Tempo) { lastBlock.Area.BarTo = block.Area.BarTo; lastBlock.MidiTimeTo = block.MidiTimeTo; continue; } } mergedBlocks.Add(block); block.Header.Number = mergedBlocks.Count; lastBlock = block; } return mergedBlocks; } #endregion /// /// Determines the bar number. /// /// The given division. /// The given event. /// /// Returns value. /// private int DetermineBarNumber(int givenDivision, IMidiEvent givenEvent) { //// out byte metricGround, out int barDivision, out int barNumber var metricGround = MusicalProperties.GetMetricGround(this.currentRecord.MetricBase); var barDivision = MusicalProperties.BarDivision(givenDivision, this.currentRecord.MetricBeat, metricGround); var barNumber = (int)Math.Floor((double)givenEvent.StartTime / barDivision) + 1; return barNumber; } /// /// Reads the current values. /// /// The Midi Event. private void ReadCurrentValues(IMidiEvent ev) { Contract.Requires(ev != null); var eventType = ev.EventType; this.currentRecord.EventType = eventType; switch (eventType) { case "MetaTempo": { this.currentRecord.Tempo = ((MetaTempo)ev).Tempo; break; } case "MetaKeySignature": { this.currentRecord.TonalityKey = ((MetaKeySignature)ev).Key; break; } case "MetaTimeSignature": { var ts = (MetaTimeSignature)ev; this.currentRecord.MetricBeat = ts.Numerator; this.currentRecord.MetricBase = ts.Denominator; break; } //// resharper default: { break; } } } /// /// Determines the type of change. /// /// The last tempo. /// The last metric beat. /// The last metric base. /// /// Returns value. /// private MidiStreamChange DetermineTypeOfChange(int lastTempo, byte lastMetricBeat, byte lastMetricBase) { //// TonalityKey lastTonalityKey, //// 2014 !!?!? //// 2015/01 || (lastTonalityKey != this.currentRecord.TonalityKey) if ((lastMetricBeat != this.currentRecord.MetricBeat) || (lastMetricBase != this.currentRecord.MetricBase)) { //// || (lastTempo != this.currentRecord.Tempo)) { return MidiStreamChange.Serious; } if (lastTempo == 0 && this.currentRecord.Tempo > 0) { return MidiStreamChange.Serious; } return MidiStreamChange.None; } } }